#pragma once

#include <boost/iostreams/detail/select.hpp>
#include <boost/type_traits/is_convertible.hpp>
#include <boost/mpl/and.hpp>

#include "bin_iostream.hpp"




namespace boostext
{
	namespace iostreams
	{
		using namespace boost::iostreams;




		template <typename Device>
		struct bin_stream_traits
		{
		public:

			typedef typename char_type_of<Device>::type   char_type;
			typedef typename category_of<Device>::type    mode;

			typedef typename
				boost::iostreams::select< // Dismbiguation required for Tru64.
					boost::mpl::and_<
						boost::is_convertible<mode,input>,
						boost::is_convertible<mode,output>
					>,
						bin_iostream_base<Device,boost::is_convertible<mode,direct_tag>::value>, //< Use iostream as a base, when the mode refines input/output
					boost::is_convertible<mode,input>,
						bin_istream_base<Device,boost::is_convertible<mode,direct_tag>::value>, //< Use istream as a base, when the mode refines input
					else_,
						bin_ostream_base<Device,boost::is_convertible<mode,direct_tag>::value> //< Use ostream as a base, when the mode refines output
				>::type stream_type;
		};
		///////////////////////////////////////////////////////////////////////////////////////////




		template <typename Device>
		class bin_stream
			: public bin_stream_traits<Device>::stream_type
		{
		protected:

			Device            m_device;
			endian_ordering   m_ordering;
			std::streamsize   m_pos;

		public:

			bin_stream()
				: m_ordering(system_endian_ordering)
				, m_pos(0)
			{}

			bin_stream(const Device& dev, endian_ordering ordering = system_endian_ordering)
				: m_device(dev)
				, m_ordering(ordering)
				, m_pos(0)
			{}

			template <typename A1, typename A2>
			bin_stream(A1 a1, A2 a2, endian_ordering ordering = system_endian_ordering)
				: m_device(a1, a2)
				, m_ordering(ordering)
				, m_pos(0)
			{}

			void open(const Device& dev, endian_ordering ordering = system_endian_ordering)
			{
				m_device   = dev;
				m_ordering = ordering;
				m_pos      = 0;
			}

			template <typename A1, typename A2>
			void open(A1 a1, A2 a2, endian_ordering ordering = system_endian_ordering)
			{
				m_device   = Device(a1, a2);
				m_ordering = ordering;
				m_pos      = 0;
			}

			/*bool is_open() const { return device.is_open(); }
			void close() { device.close(); }
			bool auto_close() const { return device.auto_close(); }
			void set_auto_close(bool close) { device.set_auto_close(close); }
			bool strict_sync() { return device.strict_sync(); }*/
			Device& operator*() { return m_device; }
			const Device& operator*() const { return m_device; }
			Device* operator->() { return &m_device; }
			const Device* operator->() const { return &m_device; }



			void setEndian(endian_ordering ordering) { m_ordering = ordering; }
			endian_ordering endian() const { return m_ordering; }

			std::streamsize pos() { return m_pos; }
			void seek(std::streamsize pos) { seek_abs<boost::is_convertible<mode,direct_tag>::value>(pos); }
			void seek(stream_offset off, std::ios_base::seekdir way) { seek_rel<boost::is_convertible<mode,direct_tag>::value>(off, way); }

		private:

			template <bool direct /*= false*/>
			void seek_abs(std::streamsize pos)
			{
				m_device.seek(pos, std::ios_base::beg);
				m_pos = pos;
			}

			template <>
			void seek_abs</*bool direct =*/ true>(std::streamsize pos)
			{
				verify_range(pos, (std::streamsize)0, size());
				m_pos = pos;
			}



			template <bool direct /*= false*/>
			std::streamsize seek_rel(stream_offset off, std::ios_base::seekdir way)
			{
				// seek may throw, but this leaves us in the same state as the device
				std::streamsize ret = m_device.seek(off, way);
				m_pos = ret;
				return ret;
			}

			template <>
			std::streamsize seek_rel</*bool direct =*/ true>(stream_offset off, std::ios_base::seekdir way)
			{
				switch(way)
				{
				case std::ios_base::beg:
					{
						verify_range((std::streamsize)off, (std::streamsize)0, size());
						return m_pos = off;
					}
					break;
				case std::ios_base::cur:
					{
						std::streamsize pos = m_pos + off;
						verify_range(pos, (std::streamsize)0, size());
						return m_pos += off;
					}
					break;
				case std::ios_base::end:
					{
						std::streamsize pos = m_pos + off;
						verify_range(pos, (std::streamsize)0, size());
						return m_pos += off;
					}
					break;
				default: BOOSTEXT_THROW(invalid_value_exception(way));
				}
			}
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
